/**
 * 
 */
package controller;

import components.Util;
import components.exceptions.BinaryToIntException;
import components.exceptions.IntToBinaryException;
import components.exceptions.SubArrayException;
import components.exceptions.UnexpectedOpcodeException;
import components.exceptions.UnexpectedNotifyReceivedException;

import controller.notify.Notify;

/**
 * @author Chz
 * 
 */
public class RegisterController extends Controller {

	private static RegisterController self;
	private Notify notify = null;

	private RegisterController() {

	}

	public static RegisterController getInstance() {
		if (self == null)
			self = new RegisterController();

		return self;
	}

	public synchronized void notify(Notify n) {
		this.notify = n;
	}

	public void run() {
		// DBG
//		System.out.println("in regCtroler, nofity received: "
//				+ notify.getType());
//		System.err.println("in regCtroler, nofity received: "
//				+ notify.getType());
		switch (notify.getType()) {
		case Notify.DONE:
			// IR = C(MBR)
			regs.setIR(regs.getMBR());
			// START DECODE
			try {
				decoder();
			} catch (UnexpectedOpcodeException e) {
				System.err
						.println("opcode error, please check the source code "
								+ "of decoder() and corresponding private decoder "
								+ "function in src.controller.RegisterController.java");
				e.printStackTrace();
			}
			break;
		case Notify.READ:
			System.err.println("Notify:READ received in Register Controller!");
		case Notify.WRITE:
			System.err.println("Notify:WRITE received in Register Controller!");
		default:
			System.err
					.println("fatal error in RegisterController, undefined Notify type "
							+ "received!!!!!!!!!!!!!!!!");
		}
	}

	/**
	 * main decode function, call specific decode function according to opcode.
	 * After calling this function, the only thing happened should be the change
	 * of relative registers.
	 * 
	 * @throws UnexpectedOpcodeException
	 */
	private void decoder() throws UnexpectedOpcodeException {

		// extract opcode, set OR
		try {

			int[] opcode = Util.subArray(regs.getIR(), 0, 5);
			int opcodeInt = Util.binaryToInt(opcode);
			regs.setOpcode(opcodeInt);
			regs.setOR(Util.subArray(regs.getIR(), 0, 5));
			if (opcodeInt == 1 || opcodeInt == 2 || opcodeInt == 3
					|| opcodeInt == 41 || opcodeInt == 42) {
				loadStoreInstDecoder(opcodeInt);
			} else if (opcodeInt < 8 && opcodeInt >= 4) {
				immedArithmeticLogicalInstDecoder(opcodeInt);
			} else if (opcodeInt < 17 && opcodeInt >= 10) {
				transferInstDecoder(opcodeInt);
			} else if (opcodeInt < 33 && opcodeInt >= 30) {
				shiftRotateInstDecoder(opcodeInt);
			} else if (opcodeInt < 64 && opcodeInt > 60) {
				IOInstDecoder(opcodeInt);
			} else if (opcodeInt == 0) {
				halt();
			}

		} catch (BinaryToIntException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SubArrayException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// notify ExecutionController that I'm Done!
		bus.sendNotify(new Notify(Notify.DONE),
				ExecutionController.getInstance());
	}

	private void halt() {
		System.out.println("Machine halt!!!");
		bus.setSingleStep(true);
		// System.exit(0);
	}

	/**
	 * decode load and store instruction, they are in the first table on page 5,
	 * including: LDR, STR, LDA, LDX, STX, opcodes are: 1, 2, 3, 41, 42
	 * 
	 * @param opcode
	 * @throws SubArrayException
	 * @throws UnexpectedOpcodeException
	 */

	private void loadStoreInstDecoder(int opcode) throws SubArrayException,
			UnexpectedOpcodeException {

		// tmp = IR for convince
		int[] tmp = regs.getIR();

		if (opcode != 1 && opcode != 2 && opcode != 3 && opcode != 41
				&& opcode != 42) {
			throw new UnexpectedOpcodeException("opcode: " + opcode
					+ " unexpected in the decoder function"
					+ " of load and store instructions.");
		}
		// I = IR[6]
		regs.setI(tmp[6]);

		// IX = IR[7]
		regs.setIX(tmp[7]);

		// RAR1 = IR[8-9]
		regs.setRAR1(Util.subArray(tmp, 8, 9));

		// ADDR = IR[10-15]
		regs.setADDR(Util.subArray(tmp, 10, 15));
	}

	// decode transfer instructions on the second table in page 6
	/**
	 * decode transfer instructions on the second table in page 6. including:
	 * JZ, JNE, JCC, JMP, JSR, RFS and SOB; opcodes ranging fron 10-16
	 * 
	 * @param opcode
	 * @throws SubArrayException
	 * @throws UnexpectedOpcodeException
	 */
	private void transferInstDecoder(int opcode) throws SubArrayException,
			UnexpectedOpcodeException {
		// tmp = IR for convince
		int[] tmp = regs.getIR();

		if (opcode == 12) { // JCC cc, x, address

			// I = IR[6]
			regs.setI(tmp[6]);

			// IX = IR[7]
			regs.setIX(tmp[7]);

			// CC = IR[8-9]
			regs.setCC(Util.subArray(tmp, 8, 9));

			// ADDR = IR[10-15]
			regs.setADDR(Util.subArray(tmp, 10, 15));

		} else if (opcode == 15) { // RFS immed

			// R0 = IR[10-15], The Immed protion will be the address field,
			// which stored in r0
			regs.setGPR(Util.subArray(tmp, 10, 15), 0);

			// PC = R3
			regs.setPC(regs.getGPR(3));

		} else if (opcode == 13 || opcode == 14) { // r is ignored

			// I = IR[6]
			regs.setI(tmp[6]);

			// IX = IR[7]
			regs.setIX(tmp[7]);

			// ADDR = IR[10-15]
			regs.setADDR(Util.subArray(tmp, 10, 15));

		} else if (opcode == 10 || opcode == 11 || opcode == 16) { // 10, 11, 16

			// I = IR[6]
			regs.setI(tmp[6]);

			// IX = IR[7]
			regs.setIX(tmp[7]);

			// RAR1 = IR[8-9]
			regs.setRAR1(Util.subArray(tmp, 8, 9));

			// ADDR = IR[10-15]
			regs.setADDR(Util.subArray(tmp, 10, 15));

		} else {
			throw new UnexpectedOpcodeException("opcode: " + opcode
					+ " unexpected in the decoder function"
					+ " of transfer instructions.");
		}
	}

	/**
	 * decode arithmetic and logical instructions on page 7, and some immediate
	 * arithmetic instructions on page 8. The arithmetic and logical
	 * instructions are: AMR, SMR, IAR and ISR, opcodes ranging from 04-07;
	 * immediate arithmetic instructions are: MUL, DIV, TST, AND, OR, NOT,
	 * opcode ranging from 20-25.
	 * 
	 * 
	 * @param opcode
	 * @throws SubArrayException
	 * @throws UnexpectedOpcodeException
	 */
	private void immedArithmeticLogicalInstDecoder(int opcode)
			throws SubArrayException, UnexpectedOpcodeException {
		// tmp = IR for convince
		int[] tmp = regs.getIR();

		// for arithmetic and logical instructions:
		if (opcode == 4 || opcode == 5) { // AMR, SMR

			// I = IR[6]
			regs.setI(tmp[6]);

			// IX = IR[7]
			regs.setIX(tmp[7]);

			// RAR1 = IR[8-9]
			regs.setRAR1(Util.subArray(tmp, 8, 9));

			// ADDR = IR[10-15]
			regs.setADDR(Util.subArray(tmp, 10, 15));
		} else if (opcode == 6 || opcode == 7) { // IAR and ISR, immed portion
													// is the address portion
			// RAR1 = IR[8-9]
			regs.setRAR1(Util.subArray(tmp, 8, 9));

			// IMMED = IR[10-15], immed protion
			regs.setIMMR(Util.subArray(tmp, 10, 15));
		}
		// then, for immediate operations:
		else if (opcode >= 20 && opcode <= 25) {
			// RAR1 = IR[6-7]
			regs.setRAR1(Util.subArray(tmp, 6, 7));

			// RAR2 = IR[8-9]
			regs.setRAR2(Util.subArray(tmp, 8, 9));
		} else
			throw new UnexpectedOpcodeException("opcode: " + opcode
					+ " unexpected in the decoder function"
					+ " of arithmetic and logical instructions.");

	}

	/**
	 * decode shift and rotate instructions on page 9. The instructions include
	 * SRC and RRC, which opcode is 031 and 032 respectively.
	 * 
	 * @param opcode
	 * @throws SubArrayException
	 * @throws UnexpectedOpcodeException
	 */
	private void shiftRotateInstDecoder(int opcode) throws SubArrayException,
			UnexpectedOpcodeException {
		// tmp = IR for convince
		int[] tmp = regs.getIR();

		if (opcode != 31 && opcode != 32)
			throw new UnexpectedOpcodeException("opcode: " + opcode
					+ " unexpected in the decoder function"
					+ " of shift and rotate instructions.");

		// LRR = IR[6]
		regs.setLRR(tmp[6]);

		// RAR1 = IR[7-8]
		regs.setRAR1(Util.subArray(tmp, 7, 8));

		// ALR = IR[9]
		regs.setALR(tmp[9]);

		// CNTR = IR[12-15]
		regs.setCNTR(Util.subArray(tmp, 21, 15));
	}

	/**
	 * decode IO instructions on page 10. These instructions include: IN, OUT
	 * and CHK, which opcodes are 61, 62 and 63 respectively.
	 * 
	 * @param opcode
	 * @throws SubArrayException
	 * @throws UnexpectedOpcodeException
	 */
	private void IOInstDecoder(int opcode) throws SubArrayException,
			UnexpectedOpcodeException {
		// tmp = IR for convince
		int[] tmp = regs.getIR();

		if (opcode <= 63 && opcode >= 61) {
			// RAR1 = IR[7-8]
			regs.setRAR1(Util.subArray(tmp, 7, 8));

			// DEVR = IR[11-15]
			regs.setDEVR(Util.subArray(tmp, 11, 15));
		} else
			throw new UnexpectedOpcodeException("opcode: " + opcode
					+ " unexpected in the decoder function"
					+ " of I/O instructions.");

	}

	@SuppressWarnings("unused")
	private void floatVectorDecoder() {
		// to be finished in phrase II
	}
}
